System.Span<T> , struktura

Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.

Typ Span<T> jest strukturą ref przydzielaną na stosie, a nie zarządzaną stertą. Typy struktur ref mają wiele ograniczeń, aby upewnić się, że nie mogą być promowane do zarządzanej sterty, w tym, że nie można ich wpisać w polu, nie mogą być przypisane do zmiennych typu Objectlub dynamic do dowolnego typu interfejsu, nie mogą być polami w typie odwołania i nie mogą być używane przez await granice i yield . Ponadto wywołania dwóch metod Equals(Object) i GetHashCode, zgłaszają wartość NotSupportedException.

Ważne

Ponieważ jest to typ tylko stosu, Span<T> jest nieodpowiedni dla wielu scenariuszy, które wymagają przechowywania odwołań do buforów na stercie. Dotyczy to na przykład procedur, które tworzą wywołania metody asynchronicznej. W takich scenariuszach można użyć typów uzupełniających System.Memory<T> i System.ReadOnlyMemory<T> .

W przypadku zakresów reprezentujących niezmienne lub tylko do odczytu użyj polecenia System.ReadOnlySpan<T>.

Pamięć

Obiekt Span<T> reprezentuje ciągły region dowolnej pamięci. Wystąpienie Span<T> jest często używane do przechowywania elementów tablicy lub części tablicy. W przeciwieństwie do tablicy wystąpienie Span<T> może jednak wskazywać pamięć zarządzaną, pamięć natywną lub pamięć zarządzaną na stosie. Poniższy przykład tworzy obiekt Span<Byte> na podstawie tablicy:

// Create a span over an array.
var array = new byte[100];
var arraySpan = new Span<byte>(array);

byte data = 0;
for (int ctr = 0; ctr < arraySpan.Length; ctr++)
    arraySpan[ctr] = data++;

int arraySum = 0;
foreach (var value in array)
    arraySum += value;

Console.WriteLine($"The sum is {arraySum}");
// Output:  The sum is 4950
// Create a span over an array.
let array = Array.zeroCreate<byte> 100
let arraySpan = Span<byte> array

let mutable data = 0uy
for i = 0 to arraySpan.Length - 1 do
    arraySpan[i] <- data
    data <- data + 1uy

let mutable arraySum = 0
for value in array do
    arraySum <- arraySum + int value

printfn $"The sum is {arraySum}"
// Output:  The sum is 4950

Poniższy przykład tworzy obiekt Span<Byte> z 100 bajtów pamięci natywnej:

// Create a span from native memory.
var native = Marshal.AllocHGlobal(100);
Span<byte> nativeSpan;
unsafe
{
    nativeSpan = new Span<byte>(native.ToPointer(), 100);
}
byte data = 0;
for (int ctr = 0; ctr < nativeSpan.Length; ctr++)
    nativeSpan[ctr] = data++;

int nativeSum = 0;
foreach (var value in nativeSpan)
    nativeSum += value;

Console.WriteLine($"The sum is {nativeSum}");
Marshal.FreeHGlobal(native);
// Output:  The sum is 4950
// Create a span from native memory.
let native = Marshal.AllocHGlobal 100
let nativeSpan = Span<byte>(native.ToPointer(), 100)

let mutable data = 0uy
for i = 0 to nativeSpan.Length - 1 do
    nativeSpan[i] <- data
    data <- data + 1uy

let mutable nativeSum = 0
for value in nativeSpan do
    nativeSum <- nativeSum + int value

printfn $"The sum is {nativeSum}"
Marshal.FreeHGlobal native
// Output:  The sum is 4950

W poniższym przykładzie użyto słowa kluczowego stackalloc języka C#, aby przydzielić 100 bajtów pamięci na stosie:

// Create a span on the stack.
byte data = 0;
Span<byte> stackSpan = stackalloc byte[100];
for (int ctr = 0; ctr < stackSpan.Length; ctr++)
    stackSpan[ctr] = data++;

int stackSum = 0;
foreach (var value in stackSpan)
    stackSum += value;

Console.WriteLine($"The sum is {stackSum}");
// Output:  The sum is 4950
    // Create a span on the stack.
    let mutable data = 0uy
    let stackSpan = 
        let p = NativeInterop.NativePtr.stackalloc<byte> 100 |> NativeInterop.NativePtr.toVoidPtr
        Span<byte>(p, 100)

    for i = 0 to stackSpan.Length - 1 do
        stackSpan[i] <- data
        data <- data + 1uy

    let mutable stackSum = 0
    for value in stackSpan do
        stackSum <- stackSum + int value

    printfn $"The sum is {stackSum}"
// Output:  The sum is 4950

Ponieważ Span<T> jest abstrakcją w dowolnym bloku pamięci, metody Span<T> typu i metod z parametrami Span<T> działają na dowolnym Span<T> obiekcie niezależnie od rodzaju pamięci, która hermetyzuje. Na przykład każda z oddzielnych sekcji kodu, które inicjują zakres i obliczają sumę swoich elementów, można zmienić na pojedyncze metody inicjowania i obliczania, jak pokazano w poniższym przykładzie:

public static void WorkWithSpans()
{
    // Create a span over an array.
    var array = new byte[100];
    var arraySpan = new Span<byte>(array);

    InitializeSpan(arraySpan);
    Console.WriteLine($"The sum is {ComputeSum(arraySpan):N0}");

    // Create an array from native memory.
    var native = Marshal.AllocHGlobal(100);
    Span<byte> nativeSpan;
    unsafe
    {
        nativeSpan = new Span<byte>(native.ToPointer(), 100);
    }

    InitializeSpan(nativeSpan);
    Console.WriteLine($"The sum is {ComputeSum(nativeSpan):N0}");

    Marshal.FreeHGlobal(native);

    // Create a span on the stack.
    Span<byte> stackSpan = stackalloc byte[100];

    InitializeSpan(stackSpan);
    Console.WriteLine($"The sum is {ComputeSum(stackSpan):N0}");
}

public static void InitializeSpan(Span<byte> span)
{
    byte value = 0;
    for (int ctr = 0; ctr < span.Length; ctr++)
        span[ctr] = value++;
}

public static int ComputeSum(Span<byte> span)
{
    int sum = 0;
    foreach (var value in span)
        sum += value;

    return sum;
}
// The example displays the following output:
//    The sum is 4,950
//    The sum is 4,950
//    The sum is 4,950
open System
open System.Runtime.InteropServices
open FSharp.NativeInterop

// Package FSharp.NativeInterop.NativePtr.stackalloc for reuse.
let inline stackalloc<'a when 'a: unmanaged> length : Span<'a> =
    let voidPointer = NativePtr.stackalloc<'a> length |> NativePtr.toVoidPtr
    Span<'a>(voidPointer, length)

let initializeSpan (span: Span<byte>) =
    let mutable value = 0uy
    for i = 0 to span.Length - 1 do
        span[i] <- value
        value <- value + 1uy

let computeSum (span: Span<byte>) =
    let mutable sum = 0
    for value in span do
        sum <- sum + int value
    sum

let workWithSpans () =
    // Create a span over an array.
    let array = Array.zeroCreate<byte> 100
    let arraySpan = Span<byte> array

    initializeSpan arraySpan
    printfn $"The sum is {computeSum arraySpan:N0}"

    // Create an array from native memory.
    let native = Marshal.AllocHGlobal 100
    let nativeSpan = Span<byte>(native.ToPointer(), 100)

    initializeSpan nativeSpan
    printfn $"The sum is {computeSum nativeSpan:N0}"

    Marshal.FreeHGlobal native

    // Create a span on the stack.
    let stackSpan = stackalloc 100

    initializeSpan stackSpan
    printfn $"The sum is {computeSum stackSpan:N0}"

// The example displays the following output:
//    The sum is 4,950
//    The sum is 4,950
//    The sum is 4,950

Tablice

Gdy opakowuje tablicę, Span<T> może opakowować całą tablicę, tak jak w przykładach w sekcji Pamięć . Ponieważ obsługuje fragmentowanie, Span<T> może również wskazywać dowolny ciągły zakres w tablicy.

Poniższy przykład tworzy wycinkę środkowych pięciu elementów tablicy całkowitej 10 elementów. Zwróć uwagę, że kod podwaja wartości każdej liczby całkowitej w wycinku. Jak pokazują dane wyjściowe, zmiany wprowadzone przez zakres są odzwierciedlane w wartościach tablicy.

using System;

var array = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
var slice = new Span<int>(array, 2, 5);
for (int ctr = 0; ctr < slice.Length; ctr++)
    slice[ctr] *= 2;

// Examine the original array values.
foreach (var value in array)
    Console.Write($"{value}  ");
Console.WriteLine();

// The example displays the following output:
//      2  4  12  16  20  24  28  16  18  20
module Program

open System

[<EntryPoint>]
let main _ =
    let array = [| 2; 4; 6; 8; 10; 12; 14; 16; 18; 20 |]
    let slice = Span<int>(array, 2, 5)
    for i = 0 to slice.Length - 1 do
        slice[i] <- slice[i] * 2

    // Examine the original array values.
    for value in array do
        printf $"{value}  "
    printfn ""
    0
// The example displays the following output:
//      2  4  12  16  20  24  28  16  18  20

Wycinki

Span<T> zawiera dwa przeciążenia Slice metody, które tworzą wycinkę bieżącego zakresu, który rozpoczyna się od określonego indeksu. Dzięki temu można traktować dane w Span<T> zestawie fragmentów logicznych, które mogą być przetwarzane zgodnie z potrzebami przez fragmenty potoku przetwarzania danych z minimalnym wpływem na wydajność. Na przykład, ponieważ nowoczesne protokoły serwera są często oparte na tekście, manipulowanie ciągami i podciągami jest szczególnie ważne. String W klasie główna metoda wyodrębniania podciągów to Substring. W przypadku potoków danych, które opierają się na obszernej manipulacji ciągami, jego użycie oferuje pewne kary za wydajność, ponieważ:

  1. Tworzy nowy ciąg do przechowywania podciągów.
  2. Kopiuje podzbiór znaków z oryginalnego ciągu do nowego ciągu.

Tę operację alokacji i kopiowania można wyeliminować przy użyciu metody Span<T> lub ReadOnlySpan<T>, jak pokazano w poniższym przykładzie:

using System;

class Program2
{
    static void Run()
    {
        string contentLength = "Content-Length: 132";
        var length = GetContentLength(contentLength.ToCharArray());
        Console.WriteLine($"Content length: {length}");
    }

    private static int GetContentLength(ReadOnlySpan<char> span)
    {
        var slice = span.Slice(16);
        return int.Parse(slice);
    }
}
// Output:
//      Content length: 132
module Program2

open System

let getContentLength (span: ReadOnlySpan<char>) =
    let slice = span.Slice 16
    Int32.Parse slice

let contentLength = "Content-Length: 132"
let length = getContentLength (contentLength.ToCharArray())
printfn $"Content length: {length}"
// Output:
//      Content length: 132